import os
import time
import py7zr
import zipfile
from subprocess import Popen, PIPE
from threading import Thread, Lock

from src.settings import Settings
from src.logger import Logger
from src.user_data import UserData


class ExeControl:
    def __init__(self, conf: UserData, logger: Logger):
        self.__control_path = Settings.WORK_PATH
        self.__conf = conf
        self.__ini = self.__conf.iniCls
        self.__logger = logger
        # 内部参数
        self.__server_exe: Popen
        self.__control_close = False
        self.__exe_alive = False
        self.__continue_restart = True
        self.__exe_start_lock = Lock()
        self.__thread_list: list = []

    # 启动服务器的实现
    def __exeStartWay(self) -> None:
        # 回到初始工作目录
        os.chdir(self.__control_path)
        if self.__ini.exe_dir:
            os.chdir(self.__ini.exe_dir)
        # 判断是否为ll服务器
        if self.__ini.exe_name == "bedrock_server.exe":
            if os.path.exists("bedrock_server_mod.exe"):
                print("[GLF机器人] 检测到 bedrock_server_mod.exe")
                print("[GLF机器人] 将为你自动启动 bedrock_server_mod.exe")
                self.__ini.exe_name = "bedrock_server_mod.exe"
        try:
            self.__server_exe = Popen(self.__ini.exe_name, stdout=PIPE, stdin=PIPE, shell=False)
        except Exception as err:
            self.__logger.add_log(f"[GLF机器人][ERROR][JAR] 启动失败，请检查启动命令! {err}")
            return
        # 成功启动
        self.__exe_alive = True
        self.__continue_restart = True
        self.__logger.add_log("[GLF机器人][INFO][JAR] 服务器开始运行")
        # 循环判断是否结束线程
        while True:
            time.sleep(1)
            if not self.__exe_alive or self.__control_close:
                try:
                    # 尝试正常关闭
                    self.__server_exe.stdin.write(f"{self.__ini.exe_stop_cmd}\n".encode())
                    self.__server_exe.stdin.flush()
                    self.__server_exe.wait(self.__ini.exe_stop_wait)
                except Exception:
                    # 强制关闭程序
                    self.__server_exe.kill()
                    self.__server_exe.wait()
                self.__logger.add_log("[GLF机器人][INFO][JAR] 服务器停止运行")
                break

    # 获取并编码下一条输出
    def __fetch_next_line(self) -> str:
        # 注意：readLine阻塞进程
        next_bytes: bytes = self.__server_exe.stdout.readline()
        next_line: str = ""
        try:
            try:
                next_line = next_bytes.decode()
            except UnicodeDecodeError:
                next_line = next_bytes.decode("gbk")
        except Exception as err:
            with open(os.path.join(self.__control_path, "dlslog.txt"), "ab") as f:
                f.write(next_bytes)
            self.__logger.add_log(f"[GLF机器人][ERROR][OUT] 错误的输出编码: {err}")
        return next_line

    # 辅助线程：重定向输出
    def __output(self) -> None:
        while not self.__control_close:
            time.sleep(0.01)
            if self.__exe_alive:
                next_line = self.__fetch_next_line()
                self.__logger.add_log(next_line)
                # 若程序终止打印完剩余的输出
                while not self.__exe_alive:
                    next_line = self.__fetch_next_line()
                    if next_line != "":
                        self.__logger.add_log(next_line)
                    else:
                        break

    # 辅助线程：判断运行状况
    def __ifExit(self) -> None:
        failed_times = 0
        record_time = time.time()
        while not self.__control_close:
            time.sleep(1)
            if self.__exe_alive:
                status_code = self.__server_exe.poll()
                # 返回码非None代表进程已结束
                if status_code is not None:
                    self.__exe_alive = False
                    # 判断是否崩溃
                    if status_code != 0:
                        if self.__ini.exe_re_alive:
                            # 按信号不再继续重启
                            if not self.__continue_restart:
                                continue
                            # 超出时限先重置计时与计数
                            interval = time.time() - record_time
                            if interval > self.__ini.exe_re_alive_limit:
                                record_time = time.time()
                                failed_times = 0
                            # 对指定秒内崩溃进行计数
                            failed_times += 1
                            if failed_times > 2:
                                self.__logger.add_log("\n[GLF机器人][INFO][JAR] 检测到连续崩溃，停止重启")
                                failed_times = 0
                                self.__continue_restart = False
                                continue
                            # 进行重启
                            self.__logger.add_log(f"\n[GLF机器人][WARN][JAR]<{status_code}> 进程崩溃，将在5秒后重启\n")
                            time.sleep(5)
                            self.exeStart()
                        else:
                            self.__logger.add_log("\n[GLF机器人][WARN][JAR] 进程崩溃，未设置重启\n")

    # 辅助线程：时钟(定时任务)
    def __clock(self) -> None:
        while not self.__control_close:
            time.sleep(1)
            if self.__ini.exe_bak:
                now_day = time.strftime("%a", time.localtime())
                now_time = time.strftime("%H%M%S", time.localtime())
                if now_day in self.__ini.re_bak_day:
                    if now_time in self.__ini.re_bak_time:
                        self.bak_world()

    # 重定向输入传入
    def executeCmd(self, command: str) -> bool:
        if self.__exe_alive:
            command = command + "\n"
            byte_command: bytes = command.encode()
            try:
                # 命令执行
                self.__server_exe.stdin.write(byte_command)
                self.__server_exe.stdin.flush()
                return True
            except Exception as err:
                self.__logger.add_log(f"[GLF机器人][WARN][CMD] 执行失败: {err}")
        elif command == "start":
            self.exeStart()
        else:
            self.__logger.add_log("[GLF机器人][INFO][CMD] 服务器未开启！")

    # 备份存档的实现
    def bak_world(self) -> None:
        # 关服备份
        self.exeStop()
        try:
            self.__logger.add_log("\n------======>开始备份<======------")
            self.__logger.add_log("[GLF机器人][INFO][BAK] 服务器开始备份")
            # 备份文件夹不存在则递归创建
            if not os.path.exists(self.__ini.bak_dir):
                os.makedirs(self.__ini.bak_dir, exist_ok=True)
            # 设定压缩目录与压缩包名
            bak_time = time.strftime(self.__ini.bak_name, time.localtime())
            zip_name = os.path.join(self.__ini.bak_dir, f"{bak_time}")
            # 根据配置选择压缩方式
            if self.__ini.bak_seven_zip:
                zip_name += ".7z"
                with py7zr.SevenZipFile(zip_name, 'w') as archive:
                    for world_dir in self.__ini.world_dirs:
                        if not os.path.exists(world_dir):
                            self.__logger.add_log(f"[GLF机器人][INFO][BAK] 备份路径{world_dir}不存在！")
                            continue
                        inner_path = os.path.split(world_dir)[1]
                        archive.writeall(world_dir, inner_path)
            else:
                zip_name += ".zip"
                with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as archive:
                    for world_dir in self.__ini.world_dirs:
                        if not os.path.exists(world_dir):
                            self.__logger.add_log(f"[GLF机器人][INFO][BAK] 备份路径{world_dir}不存在！")
                            continue
                        inner_dir = os.path.split(world_dir)[1]
                        for dirpath, _, filenames in os.walk(world_dir):
                            for filename in filenames:
                                file_path = os.path.join(dirpath, filename)
                                target_path = inner_dir + file_path.replace(world_dir, "")
                                archive.write(file_path, target_path)
            # 输出结果信息
            file_size = format(os.path.getsize(zip_name) / 1000, '.2f')
            self.__logger.add_log(f"[GLF机器人][INFO][BAK] 备份保存至: {zip_name}")
            self.__logger.add_log(f"[GLF机器人][INFO][BAK] 服务器备份完毕，存档大小 {file_size}KB")
            self.__logger.add_log("------======>备份完毕<======------\n")
            # 清理存档
            self.clearWorldBak()
        except Exception as err:
            self.__logger.add_log(f"[GLF机器人][INFO][BAK] 备份异常: {err}")
        finally:
            # 无论成功与否启动服务器
            self.exeStart()

    # 清理存档的实现
    def clearWorldBak(self) -> None:
        # 未启用不执行
        if self.__ini.bak_limit_num <= 0:
            return
        bak_files: list = [os.path.join(self.__ini.bak_dir, file)
                           for file in os.listdir(self.__ini.bak_dir)]
        over_limit_num = len(bak_files) - self.__ini.bak_limit_num
        # 未超出限制不执行
        if over_limit_num <= 0:
            return
        bak_files_date: list = [os.path.getctime(file) for file in bak_files]
        sorted_date: list = bak_files_date.copy()
        sorted_date.sort()
        for i in range(over_limit_num):
            os.remove(bak_files[bak_files_date.index(sorted_date[i])])

    # 线程生成与管理
    def __myThread(self, func) -> None:
        th = Thread(target=func, daemon=True)
        th.start()
        self.__thread_list.append(th)

    # 单独启动服务器
    def exeStart(self) -> None:
        # 互斥锁保证代码不同时运行
        with self.__exe_start_lock:
            if not self.__exe_alive:
                self.__myThread(self.__exeStartWay)
            else:
                self.__logger.add_log("[GLF机器人][INFO][CMD] 服务器已开启!")

    # 单独关闭服务器
    def exeStop(self) -> None:
        with self.__exe_start_lock:
            if not self.__exe_alive:
                self.__logger.add_log("[GLF机器人][INFO][CMD] 服务器已关闭!")
            else:
                self.__exe_alive = False
                self.__server_exe.wait()

    # 等待线程运行
    def wait(self) -> None:
        for th in self.__thread_list:
            th.join()
        # 清空线程列表
        self.__thread_list.clear()

    # 启动服务器包括监听器
    def startAll(self) -> None:
        self.__control_close = False
        self.exeStart()
        self.__myThread(self.__output)
        self.__myThread(self.__ifExit)
        self.__myThread(self.__clock)

    # 关闭服务器包括监听器
    def stopAll(self) -> None:
        self.__control_close = True
        self.wait()

    # 重新加载配置
    def reload(self) -> None:
        self.__ini = self.__conf.iniCls
